package com.zeng.ssm.service.support.idempotent;

import com.google.common.base.Optional;
import com.zeng.ssm.api.support.idempotent.DistributedLockService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.Assert;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * Redis分布式锁实现（Lua脚本加锁、解锁）
 *
 * @author zengwanlin
 * @date 2021-11-01T9:51
 */
public class RedisDistributedLockServiceImpl implements DistributedLockService {

    private final Logger logger = LoggerFactory.getLogger(RedisDistributedLockServiceImpl.class);

    private RedisTemplate redisTemplate;

    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 加锁，Lua脚本
     */
    private static final String LOCK_LUA_SCRIPT = "if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 then return redis.call('SETEX', KEYS[1], KEYS[2], ARGV[1]) else return 'failed' end";

    /**
     * 解锁，Lua脚本
     */
    private static final String UNLOCK_LUA_SCRIPT = "if redis.call('exists', KEYS[1]) == 0 then return 1 elseif redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    private static final DefaultRedisScript<String> LOCK_REDIS_SCRIPT = new DefaultRedisScript<>(LOCK_LUA_SCRIPT, String.class);

    private static final DefaultRedisScript<Boolean> UNLOCK_REDIS_SCRIPT = new DefaultRedisScript<>(UNLOCK_LUA_SCRIPT, Boolean.class);

    private static final StringRedisSerializer STRING_REDIS_SERIALIZER = new StringRedisSerializer(StandardCharsets.UTF_8);

    @PostConstruct
    public void afterPropertySet() {
        Assert.notNull(redisTemplate, "redisTemplate不能为空");
    }

    /**
     * 存储数据，底层调用{@code connection.setNX(K k, V v)}
     *
     * @param key   键
     * @param value 值
     * @return
     */
    @Override
    public boolean setIfAbsent(final String key, final String value) {
        Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value);
        if (logger.isDebugEnabled()) {
            logger.debug("Redis.setIfAbsent，key[{}]，value[{}]，结果[{}]", new Object[]{key, value, result});
        }
        return Optional.fromNullable(result).or(Boolean.FALSE);
    }

    /**
     * 存储数据，底层调用{@code connection.setNX(K k, V v)}
     * <p>
     * setNx和setEx是分开操作的，是两条命令
     *
     * @param key     键
     * @param value   值
     * @param timeout 过期时间
     * @param unit    timeout单位
     * @return
     */
    @Override
    public boolean setIfAbsent(final String key, final String value, long timeout, TimeUnit unit) {
        Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
        if (logger.isDebugEnabled()) {
            logger.debug("Redis.setIfAbsent，key[{}]，value[{}]，timeout[{}]，结果[{}]", new Object[]{key, value, timeout, result});
        }
        return Optional.fromNullable(result).or(Boolean.FALSE);
    }

    /**
     * 删除数据
     *
     * @param key 键
     * @return
     */
    @Override
    public boolean deleteAnyway(final String key) {
        try {
            redisTemplate.delete(key);
            return true;
        } catch (Exception e) {
            logger.warn("Redis.deleteAnyway异常，" + e.getMessage(), e);
            return false;
        }
    }

    /**
     * 加锁，Lua脚本
     *
     * @param key     键(String字符串，非Json)
     * @param value   值(String字符串，非Json)
     * @param timeout 超时时间(秒)
     * @return
     */
    @Override
    public boolean lock(final String key, final String value, final long timeout) {
        String result = (String) redisTemplate.execute(LOCK_REDIS_SCRIPT, STRING_REDIS_SERIALIZER, STRING_REDIS_SERIALIZER, Arrays.asList(key, timeout), value);
        if (logger.isDebugEnabled()) {
            logger.debug("Redis加锁，key[{}]，value[{}]，timeout[{}]，结果[{}]", new Object[]{key, value, timeout, result});
        }
        return "OK".equalsIgnoreCase(result);
    }

    /**
     * 解锁，Lua脚本
     *
     * @param key   键(String字符串，非Json)
     * @param value 值(String字符串，非Json)
     * @return
     */
    @Override
    public boolean unlock(final String key, final String value) {
        Boolean result = (Boolean) redisTemplate.execute(UNLOCK_REDIS_SCRIPT, STRING_REDIS_SERIALIZER, STRING_REDIS_SERIALIZER, Arrays.asList(key), value);
        if (logger.isDebugEnabled()) {
            logger.debug("Redis解锁，key[{}]，value[{}]，结果[{}]", new Object[]{key, value, result});
        }
        return Optional.fromNullable(result).or(Boolean.FALSE);
    }

}
